package com.gitee.auth;

import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by EalenXie on 2022/2/14 10:03
 */
@Configuration
public class AuthConfig {

    @Resource
    private DataSource dataSource;

    private static final String SIGNING_KEY = "jwt";

    @Bean
    public JdbcApprovalStore jdbcApprovalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 授权码模式CodeService
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }


    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 声明 ClientDetails实现
     *
     * @return ClientDetailsService
     */
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(new BCryptPasswordEncoder());
        return jdbcClientDetailsService;
    }

    /**
     * tokenService 配置
     */
    @Bean(name = "tokenServices")
    public DefaultTokenServices tokenServices(JdbcClientDetailsService jdbcClientDetailsService, TokenStore redisTokenStore) {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(jdbcClientDetailsService);
        // 允许支持refreshToken
        tokenServices.setSupportRefreshToken(true);
        // refreshToken 不重用策略
        tokenServices.setReuseRefreshToken(false);
        //设置Token存储方式
        tokenServices.setTokenStore(redisTokenStore);
        tokenServices.setTokenEnhancer(tokenEnhancerChain());
        return tokenServices;
    }


    /**
     * 自定义TokenEnhancerChain 由多个TokenEnhancer组成
     */
    @Bean
    public TokenEnhancerChain tokenEnhancerChain() {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter(), additionalInformationTokenEnhancer()));
        return tokenEnhancerChain;
    }

    /**
     * JWT 转换器
     */
    @Bean
    JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

    /**
     * token 额外自定义信息 此例获取用户信息
     */
    @Bean
    public TokenEnhancer additionalInformationTokenEnhancer() {
        return (accessToken, authentication) -> {
            Map<String, Object> information = new HashMap<>(8);
            Authentication userAuthentication = authentication.getUserAuthentication();
            if (userAuthentication instanceof UsernamePasswordAuthenticationToken) {
                UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) userAuthentication;
                Object principal = token.getPrincipal();
                if (principal instanceof OauthUserDetails) {
                    OauthUserDetails userDetails = (OauthUserDetails) token.getPrincipal();
                    UserInfo userInfo = new UserInfo();
                    BeanUtils.copyProperties(userDetails, userInfo);
                    information.put("user_info", userInfo);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(information);
                }
            }
            return accessToken;
        };
    }


    /**
     * 禁止访问处理器
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (httpServletRequest, response, e) -> {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write("{\"code\":\"403\",\"message\":\"Forbidden\",\"data\":\"" + e.getMessage() + "\"}");
        };
    }

    /**
     * 认证异常处理器
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (httpServletRequest, response, e) -> {
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            Throwable cause = e.getCause();
            if (cause instanceof OAuth2Exception) {
                OAuth2Exception oAuth2Exception = (OAuth2Exception) cause;
                response.setStatus(oAuth2Exception.getHttpErrorCode());
                response.getWriter().write("{\"code\":\"" + oAuth2Exception.getHttpErrorCode()
                        + "\",\"message\":\"" + oAuth2Exception.getOAuth2ErrorCode() + "\",\"data\":\"" + oAuth2Exception.getMessage() + "\"}");
            } else {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.getWriter().write("{\"code\":\"401\",\"message\":\"Unauthorized\",\"data\":\"" + e.getMessage() + "\"}");
            }
        };
    }


}
